using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Principal;
using System.Runtime.InteropServices;
using System.Threading;

namespace InfiniTec.Security
{
    /// <summary>
    /// Provides the ability to translate a <see cref="NTAccount"/> to a <see cref="SecurityIdentifier"/>
    /// and vice versa, optionally using a remote computer for the translation process.
    /// </summary>
    public class IdentityResolver: IEquatable<IdentityReference>
    {
        private string _ComputerName;
        private AccountType _AccountType;
        private NTAccount _Account;
        private SecurityIdentifier _Sid;

        /// <summary>
        /// Returns the <see cref="SecurityIdentifier"/> of this instance.
        /// </summary>
        public SecurityIdentifier Sid
        {
            get
            {
                if (_Sid == null) TranslateFromNTAccount();
                return _Sid;
            }
        }
         /// <summary>
        /// Returns the <see cref="NTAccount"/> of this instance.
        /// </summary>
        public NTAccount Account
        {
            get
            {
                if (_Account == null) TranslateFromSecurityDescriptor();
                return _Account;
            }
        }

        /// <summary>
        /// Returns the <see cref="AccountType"/> of this instance
        /// </summary>
        public AccountType AccountType
        {
            get {
                if (_Sid == null) TranslateFromNTAccount();
                else if (_Account == null) TranslateFromSecurityDescriptor();

                return _AccountType; 
            }
        }

        /// <summary>
        /// Creates a new instance of this class. A remote computer is not used for the 
        /// translation process
        /// </summary>
        /// <param name="identity">The identity to translate</param>
        public IdentityResolver(IdentityReference identity): this(identity, null) { }

        /// <summary>
        /// Creates a new instance of this class.
        /// </summary>
        /// <param name="identity">The identity to translate</param>
        /// <param name="computerName">The computer to use for the translation</param>
        /// <remarks>The remote computer is not used for translation, if
        /// the provided identity is a <see cref="SecurityIdentifier"/> and the domain
        /// sid of that identity equals the domain sid of the current user</remarks>
        public IdentityResolver(IdentityReference identity, string computerName)
        {
            _Sid = identity as SecurityIdentifier;
            _Account = identity as NTAccount;

            if (_Sid == null || !_Sid.IsEqualDomainSid(WindowsIdentity.GetCurrent().User))
            {
                _ComputerName = computerName;
            }

        }

        private void TranslateFromNTAccount()
        {
            byte[] binarySid;
            uint binarySidLength;
            StringBuilder referencedDomain;
            uint referencedDomainLength;
            bool result;

            binarySid = new byte[SecurityIdentifier.MaxBinaryLength];
            binarySidLength = (uint) binarySid.Length;
            referencedDomain = new StringBuilder(0xff);
            referencedDomainLength = (uint) referencedDomain.Capacity;

            result = NativeMethods.LookupAccountName(_ComputerName, _Account.Value,
                binarySid, ref binarySidLength, referencedDomain,
                ref referencedDomainLength, out _AccountType);

            if (!result) throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());

            _Sid = new SecurityIdentifier(binarySid, 0);

        }

        private void TranslateFromSecurityDescriptor()
        {
            byte[] binarySid;
            StringBuilder name;
            StringBuilder domain;
            uint nameLength;
            uint domainLength;
            bool result;

            name = new StringBuilder(0x100);
            domain = new StringBuilder(0xff);

            nameLength = (uint) name.Capacity - 2;
            domainLength = (uint) domain.Capacity - 2;

            binarySid = new byte[_Sid.BinaryLength];
            _Sid.GetBinaryForm(binarySid, 0);

            result = NativeMethods.LookupAccountSid(
                _ComputerName, binarySid, name, ref nameLength, domain, ref domainLength, out _AccountType);

            if (!result) throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());

            _Account = new NTAccount(domain.ToString(), name.ToString());
        }
      
        #region IEquatable<NTAccount> Members

        /// <summary>
        /// Compares this identity with the specified othe identity.
        /// </summary>
        /// <param name="other">The identity to compare this identity with.</param>
        /// <returns>True, if both identities represent the same identity</returns>
        public bool Equals(IdentityReference other)
        {
            if (other == null) return false;

            if (other is NTAccount) return Account.Equals(other);
            else if (other is SecurityIdentifier) Sid.Equals((SecurityIdentifier) other);

            return false;
        }

        #endregion
    }
}
